Abel'Blog

我干了什么?究竟拿了时间换了什么?

0%

go[3]-接口

概述

接口是对类型行为的抽象和概括。接口抽象的方法可以让我们函数更加灵活和适应性。中间只定义了函数类型。接口就是约定。

接口本事存储数据内容为:

type: xxx
value: xxx

定义实例

1
2
3
4
type interfaceName interface {
Write([]byte)
Read([]byte)
}

包含nil指针的接口不是nil接口

1
2
3
4
5
6
7
8
9
func main() {
var buf *bytes.Buffer
f(buf)
}
func f(out io.Writer) {
if out != nil {
out.Write([]byte("done!\n"))
}
}

虽然已经对out做了空的判断,但是依然会崩溃。原因是这个调用f函数的时候,给了一个空的*bytes.Buffer指针。所以接口本身不是空。

类型断言

x为一个interface,T为一个定义的类型。

1
2
3
4
var w io.Writer
w = os.Stdout
f := w.(*os.File) // success
e := w.(*bytes.Buffer) // panic

如果转换失败了,将会直接造成panic。如果断言操作的对象是一个nil接口,无论被断言成什么类型都会失败。

使用两个返回值方式的,第二个参数将会返回是否转换成功。

1
b,ok :=w.(*bytes.Buffer)

我们可以拿着ok变量来做逻辑。

对于类型的switch case语句可以这样来编写

1
2
3
4
5
6
7
8
func sqlQuote(x interface{}) string {
switch x:=x.(type) {
case nil:
return "NULL"
case int,uint:
return fmt.Sprintf("%d",x)
default:
panic(fmt.Sprintf("unexpected type: %T: %v",x,x))

一些建议

设计一个新的包时,先创建一个接口的集合开始,后面定义满足它们具体的类型。每个接口都实现一个。请不要这么做,这种接口是不必要去抽象。只有两个或者以上的具体类型必须以相同的方法进行处理时候才需要。ask only for what you need.(奥卡姆剃刀)

使用golang组合时常犯的错

类关系:

yRbZk9.png

当有一个B类,转换成InterfaceA提供给外部。

外部直接调用FunA。

classA类里面实现的FunA方法调用了FunB,这个时候将会造成问题。

当函数调到FunA的时候,就进入了classA类中了,调用了classA.FunB函数,而不是调用classB.FunB函数。

有些人为了解决这个问题,直接将FunA参数表里面增加一个InterfaceA的原始指针。

这种情况下,最好是对外的函数不要互相调用

测试源码

可以按照下面的方式来实现这个结构:

yRbeYR.png

对外部提供的接口 IMain 。

内部实现时 ISub , IMain 聚合了 ISub ,而且比 ISub 多了一些公开的函数。

Main类里面没有任何的成员变量,是对工作流程的一种调配,不实现任何具体的功能。它内部聚合的 ISub 的实现,都依赖于 CSpecA,CSpecB。

CBase类有成员变量,在这块实现具体的功能。在CSpecA中直接可以将CBase类已经实现的函数,做特例化修改。

这样将会让程序更加的干净。

引用